// N1Thread.cpp: implementation of the N1Thread class.
//
//////////////////////////////////////////////////////////////////////

#include "N1Thread.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

//N1ThreadSingleton<N1Thread*>  gThread = (void*)NULL;

#ifndef WINDOWS
#include <pthread.h>
#endif

static bool   gN1ThreadInitialized;

#ifdef WINDOWS
static DWORD         gN1ThreadStorageIndex;
#else
static pthread_key_t gN1ThreadStorageIndex;
#endif

typedef struct {
    N1Dictionary              *dict;
    N1AutoreleasePool         *pool;
    N1Thread                  *thread;
} N1_THREAD_STORAGE, *LPN1_THREAD_STORAGE;

LPN1_THREAD_STORAGE N1GetThreadStorage()
{
    LPN1_THREAD_STORAGE ts;
#ifdef WINDOWS
    ts = (LPN1_THREAD_STORAGE)TlsGetValue(gN1ThreadStorageIndex);
#else
    ts = (LPN1_THREAD_STORAGE)::pthread_getspecific(gN1ThreadStorageIndex);
#endif

    if (ts==NULL)
    {
        ts = (LPN1_THREAD_STORAGE)N1Malloc( sizeof(N1_THREAD_STORAGE) );
        N1MLT_AddToStaticPool(ts);
#ifdef WINDOWS
        if (!TlsSetValue(gN1ThreadStorageIndex, ts))
#else
        if (::pthread_setspecific(gN1ThreadStorageIndex, ts));
#endif
        {
            N1Log(N1Log_Error, "TlsSetValue failed");
            abort();
        }

        memset(ts, 0, sizeof(N1_THREAD_STORAGE));
    } 

    return ts;
}

void N1ThreadInitialize()
{
    if(!gN1ThreadInitialized)
    {
        int ret;
#ifdef WINDOWS
        gN1ThreadStorageIndex = TlsAlloc();
        ret = (gN1ThreadStorageIndex == TLS_OUT_OF_INDEXES);
#else
        ret = ::pthread_key_create(&gN1ThreadStorageIndex, NULL);
#endif
        if(ret)
        {
            N1Log(N1Log_Error, "Creating TSL failed");
            abort(); 
        }
        gN1ThreadInitialized = true;
    }
}

void N1ThreadCleadBeforeExitThread()
{
    LPN1_THREAD_STORAGE ts = N1GetThreadStorage();

    if(ts->dict)
    {
        ts->dict->release();
    }

    if(ts->pool)
    {
        N1Log(N1Log_Error, "Last autorelease pool not deallocated.");
    }

    if(ts->thread)
    {
        ts->thread->release();
    }

    N1Free( ts );
}

N1UInt32 N1Thread::m_nCount = 1;

N1Thread::N1Thread() : m_nLastTLS(0) {
    m_nCount++;
}

N1Thread::~N1Thread() {
    m_nCount--;
}

pcstr N1Thread::name() const 
{ 
    return "N1Thread"; 
}

#ifdef WINDOWS
typedef struct 
{
    N1DelegatePtr<void, void*> *m_f1;
    void (*m_function)(void*);
    void                       *m_p1;
} N1_THREAD_PARAM, *LPN1_THREAD_PARAM;

DWORD WINAPI N1ThreadProc(LPVOID lpParameter) 
{
    LPN1_THREAD_PARAM tp = (LPN1_THREAD_PARAM)lpParameter;

    do 
    {
        if(tp->m_f1)
        {
            tp->m_f1->invoke(tp->m_p1);  

            delete tp->m_f1;
        } 
        else if(tp->m_function)
        {
            tp->m_function(tp->m_p1);
        }
    } 
    while(0);
    
    N1Free( tp );

    N1ThreadCleadBeforeExitThread();

    return 0;
}
#endif //WINDOWS

void N1Thread::detachNewThread(void(*function)(void*), void* p1)
{
    LPN1_THREAD_PARAM tp = (LPN1_THREAD_PARAM)N1Malloc( sizeof(N1_THREAD_PARAM) );
    N1MLT_AddToStaticPool(tp);

    memset(tp, 0, sizeof(N1_THREAD_PARAM));

    tp->m_function = function;
    tp->m_p1 = p1;

#ifdef WINDOWS
    DWORD  nThreadId = 0;
    HANDLE hThread = CreateThread(NULL, 
                                  4*1024,       // initial thread stack size
                                  N1ThreadProc, // pointer to thread function
                                  tp,           // argument for new thread
                                  0,            // creation flags
                                  &nThreadId    // pointer to receive thread ID
                                 );
    hThread = NULL;
#else
    pthread_t thread;

    if (::pthread_create(&thread, NULL, N1ThreadProc, tp) == 0)
        ::pthread_detach( thread );
    else
    {
        N1Log( @"pthread_create() failed (%s:%d)", __FILE__, __LINE__ );
    }
#endif
}


void N1Thread::detachNewThread(N1DelegatePtr<void, void*> *ptr, void* p1)
{
    LPN1_THREAD_PARAM tp = (LPN1_THREAD_PARAM)N1Malloc( sizeof(N1_THREAD_PARAM) );
    N1MLT_AddToStaticPool(tp);

    tp->m_f1 = ptr;
    tp->m_p1 = p1;

#ifdef WINDOWS
    DWORD  nThreadId = 0;
    HANDLE hThread = CreateThread(NULL, 
                                  4*1024,       // initial thread stack size
                                  N1ThreadProc, // pointer to thread function
                                  tp,           // argument for new thread
                                  0,            // creation flags
                                  &nThreadId    // pointer to receive thread ID
                                 );
    hThread = NULL;
#else
    pthread_t thread;

    if (::pthread_create(&thread, NULL, N1ThreadProc, tp) == 0)
        ::pthread_detach( thread );
    else
    {
        N1Log( @"pthread_create() failed (%s:%d)", __FILE__, __LINE__ );
    }
#endif
}

void N1Thread::sleep(N1UInt32 ms)
{
    Sleep(ms);
}

bool N1Thread::isMultiThreaded() 
{
    return (m_nCount>1);
}

uint32 N1Thread::addTLS()
{
    if(m_nLastTLS==N1ThreadMaxTLS)
    {
        N1Log(N1Log_Error, "Max TLS count reached.");
        abort();
    }
    memset(&m_TLS[m_nLastTLS], 0, sizeof(N0Pointer));

    return m_nLastTLS++;
}

void*  N1Thread::getTLS(uint32 index)
{
    return m_TLS[index];
}

void   N1Thread::setTLS(uint32 index, void* data)
{
    m_TLS[index] = data;
}

void N1Thread::exit() 
{
}

N1Thread* N1Thread::currentThread()
{
    LPN1_THREAD_STORAGE ts = N1GetThreadStorage();
    if(!ts->thread)
    {
        ts->thread = new N1Thread();
    }
    return ts->thread;
}

N1Dictionary* N1Thread::getThreadDictionary()
{
    LPN1_THREAD_STORAGE ts = N1GetThreadStorage();
    if (!ts->dict)
    { 
        ts->dict = new N1Dictionary();
    }
    return ts->dict;
}

    
N1AutoreleasePool* N1Thread::getThreadLastAutoreleasePool()
{
    LPN1_THREAD_STORAGE ts = N1GetThreadStorage();
    return ts->pool;
}

N1AutoreleasePool* N1Thread::setThreadLastAutoreleasePool(N1AutoreleasePool* newPool)
{
    LPN1_THREAD_STORAGE ts = N1GetThreadStorage();
    N1AutoreleasePool* pool = ts->pool;
    ts->pool = newPool;
    return pool;
}

/**********************************************************
*********************** TESTS *****************************
**********************************************************/
#ifdef N1LIB_SELFTEST

class NewThread {
public:
    static volatile int g_Count;

    void newThread(void*)
    {
        g_Count++;
    }
};

volatile int NewThread::g_Count = 0;

N1TEST(N1ThreadDetachNewThread1)
{
    NewThread t;

    N1Thread::detachNewThread(
        new N1Delegate<NewThread, void,void*>(&t, &NewThread::newThread), 
        NULL
    );

    while(NewThread::g_Count==0)
    {
        N1Thread::sleep(10);
    }
}

#endif //N1LIB_SELFTEST
